﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SafeScript.MetaModel.Statements
{
    public enum BinaryOperatorKind
    {
        ASSIGN = SafeScript.SafeScriptConstants.ASSIGN,
        ASSIGNADD = SafeScript.SafeScriptConstants.ASSIGNADD,
        ASSIGNSUB = SafeScript.SafeScriptConstants.ASSIGNSUB,
        ASSIGNMUL = SafeScript.SafeScriptConstants.ASSIGNMUL,
        ASSIGNDIV = SafeScript.SafeScriptConstants.ASSIGNDIV,
        ASSIGNAND = SafeScript.SafeScriptConstants.ASSIGNAND,
        ASSIGNOR = SafeScript.SafeScriptConstants.ASSIGNOR,
        ASSIGNXOR = SafeScript.SafeScriptConstants.ASSIGNXOR,
        ADD = SafeScript.SafeScriptConstants.ADD,
        SUB = SafeScript.SafeScriptConstants.SUB,
        MUL = SafeScript.SafeScriptConstants.MUL,
        DIV = SafeScript.SafeScriptConstants.DIV,
        MODULO = SafeScript.SafeScriptConstants.MODULO,
        LESS = SafeScript.SafeScriptConstants.LESS,
        GREATER = SafeScript.SafeScriptConstants.GREATER,
        LESSOREQUAL = SafeScript.SafeScriptConstants.LESSOREQUAL,
        GREATEROREQUAL = SafeScript.SafeScriptConstants.GREATEROREQUAL,
        EQUAL = SafeScript.SafeScriptConstants.EQUAL,
        NOTEQUAL = SafeScript.SafeScriptConstants.NOTEQUAL,
        AND = SafeScript.SafeScriptConstants.AND,
        OR = SafeScript.SafeScriptConstants.OR,
        ARITHAND = SafeScript.SafeScriptConstants.ARITHAND,
        ARITHOR = SafeScript.SafeScriptConstants.ARITHOR,
        ARITHXOR = SafeScript.SafeScriptConstants.ARITHXOR,
        LEFT_SHIFT = SafeScript.SafeScriptConstants.LEFT_SHIFT,
        RIGHT_SHIFT = SafeScript.SafeScriptConstants.RIGHT_SHIFT
    }

    public class BinaryOperator : Operator
    {
        public Operator Left;
        public Operator Right;
        public BinaryOperatorKind Kind;

        private ITypeDef Type;

        public override int GetPrecedence()
        {
            switch (this.Kind)
            {
                case BinaryOperatorKind.ASSIGN:
                case BinaryOperatorKind.ASSIGNADD:
                case BinaryOperatorKind.ASSIGNSUB:
                case BinaryOperatorKind.ASSIGNMUL:
                case BinaryOperatorKind.ASSIGNDIV:
                case BinaryOperatorKind.ASSIGNXOR:
                case BinaryOperatorKind.ASSIGNOR:
                    return 180;
                case BinaryOperatorKind.SUB:
                case BinaryOperatorKind.ADD:
                    return 60;
                case BinaryOperatorKind.MUL:
                case BinaryOperatorKind.DIV:
                case BinaryOperatorKind.MODULO:
                    return 50;
                case BinaryOperatorKind.LESS:
                case BinaryOperatorKind.GREATER:
                case BinaryOperatorKind.LESSOREQUAL:
                case BinaryOperatorKind.GREATEROREQUAL:
                    return 80;
                case BinaryOperatorKind.EQUAL:
                case BinaryOperatorKind.NOTEQUAL:
                    return 90;
                case BinaryOperatorKind.AND:
                    return 130;
                case BinaryOperatorKind.OR:
                    return 140;
                case BinaryOperatorKind.ARITHAND:
                    return 100;
                case BinaryOperatorKind.ARITHOR:
                    return 120;
                case BinaryOperatorKind.ARITHXOR:
                    return 110;
                case BinaryOperatorKind.LEFT_SHIFT:
                case BinaryOperatorKind.RIGHT_SHIFT:
                    return 70;
            }
            // Never reached
            System.Diagnostics.Debug.Assert(false, "programming error");
            return -1;
        }

        public override ITypeDef ComputeType(ITypeDef goal, bool needsRef, ErrorReport report)
        {
            if ( this.Type != null )
                return this.Type;

            // First, check that both operators have the same or at least a compatible type
            bool isassign = false;
            if (this.Kind == BinaryOperatorKind.ASSIGN || this.Kind == BinaryOperatorKind.ASSIGNADD || this.Kind == BinaryOperatorKind.ASSIGNAND || this.Kind == BinaryOperatorKind.ASSIGNDIV || this.Kind == BinaryOperatorKind.ASSIGNMUL || this.Kind == BinaryOperatorKind.ASSIGNOR || this.Kind == BinaryOperatorKind.ASSIGNSUB || this.Kind == BinaryOperatorKind.ASSIGNXOR)
                isassign = true;
            else if (needsRef)
            {
                this.ReportNotAReferenceType(report);
                return null;
            }

            ITypeDef type1 = this.Left.ComputeType(null, isassign, report);
            ITypeDef type2 = this.Right.ComputeType(type1, false, report);

            // Error somewhere else? give up.
            if (type1 == null || type2 == null)
                return null;

            InstanceType obj1 = this.ToInstance(ref this.Left, type1, report );
            // Some error?
            if (obj1 == null)
                return null;
            InstanceType obj2 = this.ToInstance(ref this.Right, type2, report );
            // Some error?
            if (obj2 == null)
                return null;

            switch (this.Kind)
            {
                case BinaryOperatorKind.ASSIGN:
                case BinaryOperatorKind.ASSIGNADD:
                case BinaryOperatorKind.ASSIGNSUB:
                case BinaryOperatorKind.ASSIGNMUL:
                case BinaryOperatorKind.ASSIGNDIV:
                case BinaryOperatorKind.ASSIGNXOR:
                case BinaryOperatorKind.ASSIGNOR:
                    if (!obj2.CanBeAssignedTo(obj1))
                    {
                        this.ReportIncompatibleTypes(obj1, obj2, report);
                        return null;
                    }
                    this.Type = obj1;
                    break;
                case BinaryOperatorKind.SUB:
                case BinaryOperatorKind.MUL:
                case BinaryOperatorKind.DIV:
                case BinaryOperatorKind.MODULO:
                    if (obj1.Type is NumericType && obj1.Type is NumericType)
                        this.Type = obj1;
                    break;
                case BinaryOperatorKind.ADD:
                    if (obj1.Type is NumericType && obj1.Type is NumericType)
                        this.Type = obj1;
                    else if (obj1.Type is StringType && obj1.Type is StringType)
                        this.Type = obj1;
                    break;
                case BinaryOperatorKind.LESS:
                case BinaryOperatorKind.GREATER:
                case BinaryOperatorKind.LESSOREQUAL:
                case BinaryOperatorKind.GREATEROREQUAL:
                    if (obj1.IsEqualTo(obj2))
                        this.Type = new InstanceType() { Type = BoolType.Self };
                    break;
                case BinaryOperatorKind.EQUAL:
                case BinaryOperatorKind.NOTEQUAL:
                    if (obj1.IsEqualTo(obj2))
                        this.Type = new InstanceType() { Type = BoolType.Self };
                    // Allow comparison with null. If the second parameter is null, the above if clause catched this already, i.e. the null has the goal type already.
                    if (obj1.Type is NullType && obj1.CanBeAssignedTo(obj2) )
                        this.Type = new InstanceType() { Type = BoolType.Self };
                    break;
                case BinaryOperatorKind.AND:
                case BinaryOperatorKind.OR:
                    if (obj1.IsEqualTo(obj2) && (obj1.Type is BoolType))
                        this.Type = obj1;
                    break;
                case BinaryOperatorKind.ARITHAND:
                case BinaryOperatorKind.ARITHOR:
                case BinaryOperatorKind.ARITHXOR:
                case BinaryOperatorKind.LEFT_SHIFT:
                case BinaryOperatorKind.RIGHT_SHIFT:
                    if (obj1.IsEqualTo(obj2) && (obj1.Type is NumericType))
                        this.Type = obj1;
                    break;
            }

            if (this.Type == null)
                this.ReportIncompatibleTypes(obj1, obj2, report);
            return this.Type;
        }

        //private void ReportIncompatibleTypes(ITypeDef type1, ITypeDef type2, ErrorReport report)
        //{
        //    report.Add(new ErrorEntry() { Column = this.Node.StartColumn, Line = this.Node.StartLine, Filename = "?", Type = ErrorType.IncompatibleTypes });
        //}
    }
}
